﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using EmbeddedSharp.Core;
using Zove.DesignTime;
using Zove.Runtime;
using Zove.Runtime.EmbeddedSharp;
using Zove.Runtime.Core;

namespace CommonModellingRuntime.Ascend {

    public class AscendClassLoader : IClassLoader {

        public IClassFinder ClassFinder {
            get;
            set;
        }

        /// <summary>
        /// REQUIRE
        /// </summary>
        public const string KEYWORD_REQUIRE = "REQUIRE";

        /// <summary>
        /// MODEL
        /// </summary>
        public const string KEYWORD_MODEL = "MODEL";

        /// <summary>
        /// END
        /// </summary>
        public const string KEYWORD_END = "END";

        public Dictionary<string, string> DataContext {
            get;
            set;
        }

        public List<string> AssertContext {
            get;
            set;
        }

        public AscendClassLoader() {
            DataContext = new Dictionary<string, string>();
            AssertContext = new List<string>();
        }

        #region IClassLoader Members

        /// <summary>
        /// Load from source
        /// </summary>
        /// <param name="source">source</param>
        /// <returns>ZoomClass</returns>
        public Zove.DesignTime.ZoomClass LoadFromSource(string source) {
            Lexer lexer = new Lexer(source.Trim());

            return Load(lexer);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="lexer"></param>
        /// <returns></returns>
        public ZoomClass Load(Lexer lexer) {
            ZoomClass c = new ZoomClass();
            while (lexer.HasMoreTokens()) {
                Token token = lexer.NextToken(KEYWORD_MODEL, KEYWORD_REQUIRE);
                if (token.Value == KEYWORD_REQUIRE) {
                    token = ParseRequire(lexer, token);
                } else if (token.Value == KEYWORD_MODEL) {
                    token = ParseModel(lexer, c, token);
                }
            }

            return c;
        }

        /// <summary>
        /// Parse model
        /// </summary>
        /// <param name="lexer"></param>
        /// <param name="c"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        protected Token ParseModel(Lexer lexer, ZoomClass c, Token token) {
            # region class definition
            token = lexer.NextToken(TokenType.IDENTIFIER);
            c.Name = token.Value;
            c.Summary = lexer.LastComment;
            lexer.NextToken(TokenType.Semicolon);

            token = lexer.PeekToken();
            if (token.Value.Equals("refines")) {
                token = lexer.NextToken();
                token = ParseRefines(lexer, c, token);
            }

            # region class internal
            Token nt = lexer.PeekToken();
            while (token.Value != "END") {
                if (token.Value == "METHODS") {
                    token = lexer.NextToken();
                    // do nothing
                } else if (token.Value == "METHOD") {
                    lexer.NextToken();
                    ParseMethod(lexer, c);
                } else if (token.TokenType == TokenType.IDENTIFIER
                      && (
                    nt.TokenType == TokenType.IDENTIFIER
                    || nt.TokenType == TokenType.Comma)) {
                    ParseDeclaration(lexer, c);
                } else {
                    ParseEquation(lexer, c);
                }

                token = lexer.PeekToken();
                nt = lexer.PeekToken(2);
            }
            lexer.NextToken(); // should be '}'
            token = lexer.NextToken(TokenType.IDENTIFIER);
            lexer.NextToken(TokenType.Semicolon);
            #endregion
            #endregion
            return token;
        }

        protected void ParseMethod(Lexer lexer, ZoomClass c) {
            Token token = lexer.NextToken(TokenType.IDENTIFIER);
            string methodName = token.Value;
            lexer.NextToken(TokenType.Semicolon);

            token = lexer.NextToken();
            while (token.Value != "END") {
                if (token.Value == "FIX") {
                    string fix = lexer.ReadUntil(';');
                } else if (token.Value == "ASSERT") {
                    string expr = lexer.ReadUntil(';');
                    AssertContext.Add(expr);
                } else {
                    string key = token.Value;
                    lexer.NextToken(TokenType.Declare);

                    // TODO: an expression instead of a constant
                    token = lexer.NextToken(TokenType.Number);
                    string value = token.Value;
                    token = lexer.NextToken(TokenType.LeftBracket, TokenType.Semicolon);
                    if (token.TokenType == TokenType.LeftBracket) {
                        string unit = lexer.ReadUntil('}');
                        lexer.NextToken(TokenType.Semicolon);
                    }

                    DataContext.Add(key, value);
                    Console.WriteLine(key + " := " + value);
                }

                token = lexer.NextToken();
            }

            token = lexer.NextToken(TokenType.IDENTIFIER);
            lexer.NextToken(TokenType.Semicolon);
        }

        private static void ParseEquation(Lexer lexer, ZoomClass c) {
            int offset = lexer.Index;
            string ln = lexer.ReadUntil(';');
            if (ln.Contains(":=")) {
                // this is a function definition
            } else {
                string[] p = ln.Split('=');

                #region analysis
                for (int i = 0; i < p.Length; i++) {
                    List<string> id = Utils.GetAllIdentifiers(p[i].Trim());

                    for (int j = i + 1; j < p.Length; j++) {
                        List<string> right = Utils.GetAllIdentifiers(p[j].Trim());
                        right.AddRange(id);

                        ZoomEquation e = new ZoomEquation();
                        e.LineNumber = lexer.LineNumber;
                        e.Parent = c;
                        e.Left = p[i].Trim();
                        e.Right = p[j].Trim();
                        e.Equation = e.Left + "=" + e.Right;

                        foreach (string m in right) {
                            string variableName = m;
                            if (m.Contains(".")) {
                                variableName = m.Split('.')[0];
                            }
                            //object constant = GetConstant(m);
                            //if (constant != null) {
                            //    e.Equation = Utils.ReplaceString(
                            //        m, constant.ToString(), e.Equation);
                            //} else {
                            //if (c.HasVariable(variableName)) {
                            if (!ZoomClassParser.HasElement(m, e)) {
                                ZoomElement element = new ZoomElement();
                                element.Name = m;

                                e.Elements.Add(element);
                            }
                            //}
                        }
                        c.Equations.Add(e);
                    }
                }
                #endregion
            }
        }

        /// <summary>
        /// Parse a declaration
        /// </summary>
        /// <param name="lexer">the lexer</param>
        protected void ParseDeclaration(
            Lexer lexer, ZoomClass c) {
            Token token = null;

            do {
                token = lexer.NextToken(
                    TokenType.IDENTIFIER, TokenType.Semicolon);
                if (token.TokenType == TokenType.Semicolon) {
                    break;
                }

                List<string> names = new List<string>();
                names.Add(token.Value);
                token = lexer.NextToken(TokenType.IDENTIFIER, TokenType.Comma);
                if (token.TokenType == TokenType.Comma) {
                    token = lexer.NextToken(TokenType.IDENTIFIER);

                    do {
                        names.Add(token.Value);
                        token = lexer.NextToken(TokenType.IDENTIFIER);
                    } while (token.Value != "IS_A");
                }
                string define = token.Value;

                switch (define) {
                    case "IS_A":
                        ZoomVariable v = null;

                        token = lexer.NextToken(TokenType.IDENTIFIER);
                        string type = token.Value;
                        foreach (string name in names) {
                            v = new ZoomVariable();
                            v.Parent = c;
                            v.Name = name;

                            v.Type = "factor"; // token.Value;
                            v.Summary = lexer.LastComment;
                            c.Variables.Add(v);
                        }
                        break;
                    case "ALIAS":
                        throw new NotImplementedException("ALIAS");
                        break;
                    case "METHODS":
                        break;
                    case "METHOD":
                        string methodName = token.Value;
                        token = lexer.NextToken();

                        while (token.Value != "end") {
                            switch (token.Value) {
                                case "FIX":
                                    break;
                                default:
                                    break;
                            }
                        }
                        break;
                    default:
                        break;
                }
            }
            while (lexer.HasMoreTokens());
        }

        private static Token ParseRefines(Lexer lexer, ZoomClass c, Token token) {
            do {
                token = lexer.NextToken(TokenType.IDENTIFIER);
                c.BaseClasses.Add(token.Value);
                token = lexer.NextToken();
            } while (token.TokenType != TokenType.LeftBracket);
            return token;
        }

        private Token ParseRequire(Lexer lexer, Token token) {
            token = lexer.NextToken();
            StringBuilder b = new StringBuilder();
            while (lexer.HasMoreTokens()
                && token.TokenType != TokenType.Semicolon) {
                b.Append(token.Value);
                token = lexer.NextToken();
            }
            if (!lexer.HasMoreTokens()) {
                throw new UnexpectedEndOfFileException();
            }

            string path = b.ToString();
            if (!Directory.Exists(path)) {
                path = Environment.CurrentDirectory + "\\" + path;
                if (!Directory.Exists(path)) {
                    throw new DirectoryNotFoundException(
                        token.Value);
                }
            }
            ZoveDependencyManager.Instance.ClassFinder.AddClassPath(
                path);
            return token;
        }

        /// <summary>
        /// Load by class name
        /// </summary>
        /// <param name="className"></param>
        /// <returns></returns>
        public Zove.DesignTime.ZoomClass LoadByClassName(string className) {
            string location = ClassFinder.GetLocation(className);
            string source = System.IO.File.ReadAllText(location);

            return LoadFromSource(source);
        }

        public Zove.DesignTime.ZoomClass LoadFromFile(string uri) {
            throw new NotImplementedException();
        }

        #endregion
    }
}
